package hypeerweb;
import java.io.Serializable;
import java.util.*;

import proxyCommand.ObjectDB;

import visitor.FindVisitor;
import visitor.Parameters;

/**
 *
 */
public class Connections implements Serializable {

	private static final long serialVersionUID = 1L;

	private ArrayList<NodeDelegate> upPointers;
	private ArrayList<NodeDelegate> downPointers;
	private ArrayList<NodeDelegate> largerNeighbors;
	private ArrayList<NodeDelegate> smallerNeighborsWithChildren;
	private ArrayList<NodeDelegate> smallerNeighborsWithoutChildren;

	private NodeDelegate fold;
	private Node surrogateFold;
	private Node inverseSurrogateFold;

	private Node myNode;//the node who owns the connections

	public Connections(Node node) {
		myNode = node;
		upPointers = new ArrayList<NodeDelegate>();
		downPointers = new ArrayList<NodeDelegate>();
		largerNeighbors = new ArrayList<NodeDelegate>();
		smallerNeighborsWithoutChildren = new ArrayList<NodeDelegate>();
		smallerNeighborsWithChildren = new ArrayList<NodeDelegate>();

		fold = new Fold(node);
		surrogateFold = null;
		inverseSurrogateFold = null;
	}

	public Connections(ArrayList<NodeDelegate> _downPointers, ArrayList<NodeDelegate> _smallerNeighborsWithoutChildren,
			Node smallerNeighborWithChildren, Node me) {
		myNode = me;

		this.upPointers = new ArrayList<NodeDelegate>();
		this.downPointers = new ArrayList<NodeDelegate>();
		for(NodeDelegate n : _downPointers) {
			this.downPointers.add(new DownPointer(n.delegate));
		}
		
		largerNeighbors = new ArrayList<NodeDelegate>();
		smallerNeighborsWithoutChildren = new ArrayList<NodeDelegate>();
		for(NodeDelegate n: _smallerNeighborsWithoutChildren) {
			smallerNeighborsWithoutChildren.add(new Neighbor(n.delegate)); 
		}
		
		smallerNeighborsWithChildren = new ArrayList<NodeDelegate>();
		if(smallerNeighborWithChildren != null) {
			smallerNeighborsWithChildren.add(new Neighbor(smallerNeighborWithChildren));
		}

		setNewFold(smallerNeighborWithChildren);
	}

	public Connections getChildConnections(Node child) {
		System.out.println(myNode);
		return new Connections(largerNeighbors, upPointers, myNode, child);
	}

	public ArrayList<NodeDelegate> getAllConnections() {
		ArrayList<NodeDelegate> allConnections = new ArrayList<NodeDelegate>();
		allConnections.addAll(upPointers);
		allConnections.addAll(downPointers);
		allConnections.addAll(largerNeighbors);
		allConnections.addAll(smallerNeighborsWithoutChildren);
		allConnections.addAll(smallerNeighborsWithChildren);
		allConnections.add(fold);

		if(surrogateFold != null) {
			allConnections.add(new SurrogateFold(surrogateFold));
		}

		if(inverseSurrogateFold != null) {
			allConnections.add(new InverseSurrogateFold(inverseSurrogateFold));
		}

		return allConnections;
	}

	public void setMyNode(Node node) {
		myNode = node;
	}

	protected void setFold(Node newfold) {
		if(newfold != null) {
			this.fold = new Fold(newfold);
		}
		else {
			this.fold = null;
		}
			
	}

	protected void setNewFold(Node parent) {

		/*
		 * y	x	-->	y	x	Cx
		 * -----------------------
		 * x	y		Cx	-	y
		 * -	-		-	y	-
		 * -	-		x	-	-
		 */
		if(parent.getConnections().inverseSurrogateFold == null) {
			setFold(parent.getFold());
			parent.setFold(null);
			this.fold.delegate.setFold(myNode);

			if(myNode.getWebId().getValue() != 1 || fold.delegate.getWebId().getValue() != 0) {
				this.fold.delegate.setInverseSurrogateFold(parent);
				parent.setSurrogateFold(this.fold.delegate);
			}
			//TODO - add proxy if not already in the database
		}

		/*
		 * y	x	Cx	-->	y	x	Cx	Cy
		 * --------------------------------
		 * Cx	-	y		Cx	Cy	y	x
		 * -	y	-		-	-	-	-
		 * x	-	-		-	-	-	-
		 */
		else {
			setFold(parent.getConnections().getInverseSurrogateFold());
			this.fold.delegate.setFold(myNode);
			this.fold.delegate.setSurrogateFold(null);
			parent.setInverseSurrogateFold(null);
			//TODO - add proxy if not already in the database
		}
	}

	protected Node getFold() {
		if(fold != null) {
			return fold.delegate;
		}
		else {
			return null;
		}
	}

	protected void setSurrogateFold(Node node) {
		surrogateFold = node;
		if(node != null) {
			node.setInverseSurrogateFold(this.myNode);
		}
	}

	protected void setNewSurrogateFold(Node node) {
		surrogateFold = node;
	}
	
	protected Node getSurrogateFold() {
		return surrogateFold;
	}

	protected void setInverseSurrogateFold(Node node) {
		inverseSurrogateFold = node;
	}

	protected Node getInverseSurrogateFold() {
		if(inverseSurrogateFold != null) {
			return inverseSurrogateFold;
		}
		else {
			return null;
		}
	}

	//Neighbor Items
	/*
	 *For loading a segment we add the nodes directly without any side effects
	 */
	

	protected void addToSmallWithChildren(NodeDelegate node) {

		for(int i = 0; i < smallerNeighborsWithChildren.size(); i++) {
			if(smallerNeighborsWithChildren.get(i).delegate.getWebId().getValue() > node.delegate.getWebId().getValue()) {
				smallerNeighborsWithChildren.add(i, node);
				return;
			}
		}

		smallerNeighborsWithChildren.add(node);
	}
	protected void addToSmallWithoutChildren(NodeDelegate node) {
		for(int i = 0; i < smallerNeighborsWithoutChildren.size(); i++) {
			if(smallerNeighborsWithoutChildren.get(i).delegate.getWebId().getValue() > node.delegate.getWebId().getValue()) {
				smallerNeighborsWithoutChildren.add(i, node);
				return;
			}
		}

		smallerNeighborsWithoutChildren.add(node);
	}
	protected void addToLargerNeighbors(NodeDelegate node) {
		for(int i = 0; i < largerNeighbors.size(); i++) {
			if(largerNeighbors.get(i).delegate.getWebId().getValue() > node.delegate.getWebId().getValue()) {
				largerNeighbors.add(i, node);
				return;
			}
		}
		largerNeighbors.add(node);
	}
	protected void setSimpleFold(NodeDelegate node) {
		this.fold = node;

	}
	
	protected void setSimpleInverseSurrogateFold(Node node) {
		this.inverseSurrogateFold = node;
	}
	
	protected void setSimpleSurrogateFold(Node node) {
		this.surrogateFold = node;
	}
	
	protected void addSimpleUpPointer(NodeDelegate node) {
		this.upPointers.add(node);
	}
	
	protected void addSimpleDownPointer(NodeDelegate node) {
		this.downPointers.add(node);
	}	
	
	protected void addNeighbor(NodeDelegate node) {
		if(node.getDelegate().getWebId().getValue() > this.myNode.getWebId().getValue()) {
			largerNeighbors.add(node);
			if(downPointers.size() > 0) {
				downPointers.remove(0);
			}
		}
		else {
			smallerNeighborsWithoutChildren.add(node);
		}
	}
	
	protected ArrayList<NodeDelegate> getLargerNeighbors() {
		return largerNeighbors;
	}

	protected void removeNeighbor(NodeDelegate node) {
		//largerNeighbors.remove(largerNeighbors.size() - 1);
		if(largerNeighbors.remove(node)) {
			return;
		}
		else if(smallerNeighborsWithoutChildren.remove(node)) {
			return;
		}

		smallerNeighborsWithChildren.remove(node);
	}

	protected void replaceNeighbor(NodeDelegate node, NodeDelegate replacingNode) {
		if(largerNeighbors.contains(node)) {
			largerNeighbors.set(largerNeighbors.indexOf(node), replacingNode);
		}
		else if(smallerNeighborsWithoutChildren.contains(node)) {
			smallerNeighborsWithoutChildren.set(smallerNeighborsWithoutChildren.indexOf(node), replacingNode);
		}
		else if(smallerNeighborsWithChildren.contains(node)) {
			smallerNeighborsWithChildren.set(smallerNeighborsWithChildren.indexOf(node), replacingNode);
		}
		else {

		}
	}

	protected HashSet<Node> getNeighbors() {
		HashSet<Node> neighbors = new HashSet<Node>();
		for(NodeDelegate n : largerNeighbors) {
			neighbors.add(n.delegate);
		}
		for(NodeDelegate n : smallerNeighborsWithChildren) {
			neighbors.add(n.delegate);
		}
		for(NodeDelegate n : smallerNeighborsWithoutChildren) {
			neighbors.add(n.delegate);
		}
		//System.out.println(neighbors);
		return neighbors;
	}

	/**
	 * This method should be called on all nodes after we add a cap node.
	 */
	public void shiftNeighborsRight() {
		for(NodeDelegate n : smallerNeighborsWithoutChildren) {
			smallerNeighborsWithChildren.add(n);
		}
		smallerNeighborsWithoutChildren.clear();
		for(NodeDelegate n : smallerNeighborsWithChildren) {
			smallerNeighborsWithoutChildren.add(n);
		}
		smallerNeighborsWithChildren.clear();
	}

	public void shiftNeighborsLeft() {
		for(NodeDelegate n : smallerNeighborsWithoutChildren) {
			smallerNeighborsWithChildren.add(n);
		}
		smallerNeighborsWithoutChildren.clear();
	}

	public Node getSmallestNeighborWithoutChildren() {
		if(smallerNeighborsWithoutChildren.size() > 0) {
			return smallerNeighborsWithoutChildren.get(0).delegate;
		}

		return null;
	}

	public Node getSmallestNeighborWithChildren() {
		if(smallerNeighborsWithChildren.size() > 0) {
			return smallerNeighborsWithChildren.get(0).delegate;
		}

		return null;
	}

	public Node getLargestNeighbor() {
		Node largest = myNode;
		for(Node n : getNeighbors()) {
			
			if (n.getWebId().getValue() > largest.getWebId().getValue()) {
				largest = n;
			}
		}

		for(NodeDelegate n : upPointers) {
			if (n.delegate.getWebId().getValue() > largest.getWebId().getValue()) {
				largest = n.delegate;
			}
		}

		if(largest == myNode) {
			return null;
		}
		else {
			return largest;
		}
	}


	//UpPointers
	protected void addUpPointer(NodeDelegate node) {
		upPointers.add(node);
		if(smallerNeighborsWithoutChildren.size() > 0) {
			smallerNeighborsWithChildren.add(smallerNeighborsWithoutChildren.remove(0));
		}
	}
	
	protected void addUpPointerDB(NodeDelegate node) {
		for(int i = 0; i < upPointers.size(); i++) {
			if(upPointers.get(i).delegate.getWebId().getValue() > node.delegate.getWebId().getValue()) {
				upPointers.add(i, node);
				return;
			}
		}

		upPointers.add(node);
	}

	protected final void removeUpPointer(final NodeDelegate node) {
		upPointers.remove(node);
	}

	protected final void replaceUpPointer(final NodeDelegate node, final NodeDelegate replacingNode) {
		upPointers.set(upPointers.indexOf(node), replacingNode);
	}

	protected void removeUpPointers() {
		upPointers.clear();
	}

	public Node getLargestUpPointer() {
		return getLast(upPointers);
	}

	protected ArrayList<Node> getUpPointers() {
		ArrayList<Node> upPointerDelegates = new ArrayList<Node>();
		for(NodeDelegate n: upPointers) {
			upPointerDelegates.add(n.delegate);
		}
		return upPointerDelegates;
	}
	
	protected ArrayList<NodeDelegate> getUpPointersDelegate() {
		return upPointers;
	}

	//Down Pointers
	protected void addDownPointer(NodeDelegate node) {
		for(int i = 0; i < downPointers.size(); i++) {
			if(downPointers.get(i).delegate.getWebId().getValue() > node.delegate.getWebId().getValue()) {
				downPointers.add(i, node);
				return;
			}
		}

		downPointers.add(node);
	}

	protected void removeDownPointer(NodeDelegate node) {
		downPointers.remove(node);
	}

	protected void replaceDownPointer(NodeDelegate node, NodeDelegate replacingNode) {
		downPointers.set(downPointers.indexOf(node), replacingNode);
	}

	public Node getSmallestDownPointer() {
		for(int i = 0; i < downPointers.size(); i++) {
			//if(downPointers.get(i).delegate != null) {
				return downPointers.get(i).delegate;
			//}
		}
		
		return null;
	}

	protected Node getLargestDownPointer() {
		return getLast(downPointers);
	}

	protected ArrayList<Node> getDownPointers() {
		ArrayList<Node> downPointerDelegates = new ArrayList<Node>();
		for(NodeDelegate n: downPointers) {
			if(n.delegate != null) {
				downPointerDelegates.add(n.delegate);
			}
		}
		return downPointerDelegates;
	}

	protected Node getLast(ArrayList<NodeDelegate> list) {
		if(list.size() > 0) {
			return list.get(list.size() - 1).delegate;
		}
		else {
			return null;
		}
	}

	protected void removeSmallerNeighborsChild() {
		smallerNeighborsWithoutChildren.add(0, smallerNeighborsWithChildren.remove(smallerNeighborsWithChildren.size() - 1));
	}
	public String getPrintout(Node n) {
		if(n instanceof NodeProxy) {
			return "(NP)" + n.getWebId().getValue() + "->" + n.getLocalObjectId() + ", ";
		}
		else {
			return n.getWebId().getValue() + "->" + n.getLocalObjectId() + ", ";
		}
	}
	public String toString() {
		
		String toReturn = "\tWith:";
		//System.out.println(toReturn);
		for(NodeDelegate n : smallerNeighborsWithChildren) {
			toReturn += getPrintout(n.getDelegate());
		//	System.out.println(toReturn);
		}
		toReturn += "\tWithout:";
		//System.out.println(toReturn);
		for(NodeDelegate n : smallerNeighborsWithoutChildren) {
			toReturn += getPrintout(n.getDelegate());
		//	System.out.println(toReturn);
		}
		toReturn += "\tLarger:";
		//System.out.println(toReturn);
		for(NodeDelegate n : largerNeighbors) {
			toReturn += getPrintout(n.getDelegate());
		//	System.out.println(toReturn);
		}
		toReturn += "\tUp: ";
		//System.out.println(toReturn);
		for(Node n : getUpPointers()) {
			toReturn += getPrintout(n);
		//	System.out.println(toReturn);
		}
		toReturn += "\tDown: ";
		//System.out.println(toReturn);
		for(Node n : getDownPointers()) {
			if(n != null) {
				toReturn += getPrintout(n);
		//		System.out.println(toReturn);
			}
			else {
				toReturn += "We found a null down pointer, ";	
			}
		//	System.out.println(toReturn);
		}
		toReturn += "\tFold: " + (fold != null ? getPrintout(fold.delegate) : "null");
		//System.out.println(toReturn);
		
		toReturn += "\tSurr. fold:: " + (surrogateFold != null ? getPrintout(surrogateFold) : "null");
		//System.out.println(toReturn);
		toReturn += "\tInv. fold: " + (inverseSurrogateFold != null ? getPrintout(inverseSurrogateFold) : "null");
		//System.out.println(toReturn);
		return toReturn;
	}

	public ArrayList<Node> getNeighborsForBroadcast(Node node) {
		ArrayList<Node> result = new ArrayList<Node>();

		//Get binary string of webid and get index of last occurrence of 1
		String webId = Integer.toBinaryString(myNode.getWebId().getValue());
		int maxDifference = (int)Math.pow(2, webId.length() - webId.lastIndexOf("1"));

		if(myNode.getWebId().getValue() == 0) {
			maxDifference = 1000;
		}
		//Add all nodes with webid no greater than 2^index of last occurrence of 1
		for(NodeDelegate n : this.largerNeighbors) {
			if(n.getDelegate().getWebId().getValue() - myNode.getWebId().getValue() < maxDifference) {
				result.add(n.getDelegate());
			}
		}

		return result;
	}
	/**
	 * returns the node in the hypeerweb that has nodeId as it's webid.
	 * 
	 * @pre nodeId has a corresponding node currently in they Hypeerweb
	 * 
	 * @post the node has been found, and then returned
	 * 
	 * @param node
	 * 			the node that is initiateing the findings
	 * @param nodeId
	 * 			the webid for the node you are searching for
	 * @return the found node
	 */
	public Node find(Node node, WebId nodeId) {
		//create a new visitor
		FindVisitor fv = new FindVisitor();
		//set the parameters
		Parameters params = fv.createInitialParameters(nodeId);
		//start the chain of sends
		return node.accept(fv, params);
		//when the target is reached the targetOperation puts the node into the parameters.
		//return (Node) fv.getParameters().get(fv.FINAL_NODE);
	}

	public ArrayList<NodeDelegate> getSmallerNeighborsWithChildren() {
		return smallerNeighborsWithChildren;
	}

	public ArrayList<NodeDelegate> getSmallerNeighborsWithoutChildren() {
		return smallerNeighborsWithoutChildren;
	}
}
